/*
* Sun Public License Notice
*
* The contents of this file are subject to the Sun Public License
* Version 1.0 (the "License"). You may not use this file except in
* compliance with the License. A copy of the License is available at
* http://www.sun.com/
*
* The Original Code is NetBeans. The Initial Developer of the Original
* Code is Sun Microsystems, Inc. Portions Copyright 1997-2000 Sun
* Microsystems, Inc. All Rights Reserved.
*/
package org.netbeans.modules.properties;
import java.awt.BorderLayout;
import java.awt.Image;
import java.awt.Toolkit;
import java.awt.datatransfer.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.BeanInfo;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.beans.PropertyVetoException;
import java.beans.VetoableChangeListener;
import java.io.*;
import java.lang.reflect.InvocationTargetException;
import java.text.MessageFormat;
import java.util.Enumeration;
import java.util.Set;
import java.util.HashSet;
import java.util.TreeSet;
import java.util.AbstractCollection;
import java.util.ResourceBundle;
import java.util.Collections;
import java.util.Iterator;
import java.util.Comparator;
import java.util.ArrayList;
import javax.swing.*;
import javax.swing.event.ChangeListener;
import javax.swing.event.ChangeEvent;
import org.openide.loaders.*;
import org.openide.*;
import org.openide.cookies.EditCookie;
import org.openide.util.datatransfer.*;
import org.openide.filesystems.FileLock;
import org.openide.filesystems.FileObject;
import org.openide.filesystems.FileSystem;
import org.openide.util.*;
import org.openide.util.enum.*;
import org.openide.util.actions.SystemAction;
import org.openide.util.actions.Presenter;
import org.openide.nodes.*;
/** This entry defines utility methods for finding out locale-specific data about entries.
*/
public class PropertiesFileEntry extends PresentableFileEntry {
protected String basicName;
transient protected StructHandler propStruct;
static final long serialVersionUID =-3882240297814143015L;
/** Creates new PropertiesFileEntry */
PropertiesFileEntry(MultiDataObject obj, FileObject file) {
super(obj, file);
FileObject fo = getDataObject().getPrimaryFile();
if (fo == null)
// primary file not init'ed yet => I'm the primary entry
basicName = getFile().getName();
else
basicName = fo.getName();
init();
}
/** Initializes the object after creation and deserialization */
private void init() {
// edit as a viewcookie
getCookieSet().add (new PropertiesEditorSupport(this));
}
/** Creates a node delegate for this entry. */
protected Node createNodeDelegate() {
return new PropertiesLocaleNode(this);
}
/** Constructs children for this file entry */
public Children getChildren() {
return new PropKeysChildren();
}
/** Returns a properties structure corresponding to this entry. Constructs itif necessary. */
public StructHandler getHandler() {
if (propStruct == null)
propStruct = new StructHandler(this);
return propStruct;
}
/** Deserialization */
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject();
init();
}
/** Returns editor support for properties */
protected PropertiesEditorSupport getPropertiesEditor() {
return (PropertiesEditorSupport)getCookieSet().getCookie(EditCookie.class);
}
/* Renames underlying fileobject. This implementation returns the
* same file.
*
* @param name new base name of the bundle
* @return file object with renamed file
*/
public FileObject rename (String name) throws IOException {
if (!getFile().getName().startsWith(basicName))
throw new InternalError("Never happens - error in Properties loader / rename");
FileObject fo = super.rename(name + getFile().getName().substring(basicName.length()));
basicName = name;
return fo;
}
/* Renames underlying fileobject. This implementation returns the
* same file.
*
* @param name full name of the file
* @return file object with renamed file
*/
public FileObject renameEntry (String name) throws IOException {
if (!getFile().getName().startsWith(basicName))
throw new InternalError("Never happens - error in Properties loader / rename");
if (basicName.equals(getFile().getName())) {
// primary entry - can not rename
NotifyDescriptor.Message msg = new NotifyDescriptor.Message(
NbBundle.getBundle(PropertiesDataLoader.class).
getString("MSG_AttemptToRenamePrimaryFile"),
NotifyDescriptor.ERROR_MESSAGE);
TopManager.getDefault().notify(msg);
return getFile();
}
FileObject fo = super.rename(name);
return fo;
}
public FileObject createFromTemplate (FileObject folder, String name) throws IOException {
ResourceBundle bundle = NbBundle.getBundle (PropertiesFileEntry.class);
if (! getFile ().getName ().startsWith (basicName))
throw new InternalError("Never happens - error in Properties createFromTemplate");
String suffix = getFile ().getName ().substring (basicName.length ());
String nuename = name + suffix;
String ext = getFile ().getExt ();
FileObject existing = folder.getFileObject (nuename, ext);
if (existing == null) {
return super.createFromTemplate (folder, nuename);
} else {
Object leaveAloneOpt = bundle.getString ("OPT_leave_alone");
Object concatOpt = bundle.getString ("OPT_concatenate");
Object overwriteOpt = bundle.getString ("OPT_overwrite");
String title = bundle.getString ("LBL_ask_how_to_template");
String message = MessageFormat.format (bundle.getString ("MSG_ask_how_to_template"),
new Object[] { nuename });
NotifyDescriptor desc = new NotifyDescriptor
(message, title, NotifyDescriptor.DEFAULT_OPTION,
NotifyDescriptor.QUESTION_MESSAGE,
new Object[] { concatOpt, leaveAloneOpt, overwriteOpt },
concatOpt); // [PENDING] default option does not seem to work--so make it 1st
Object result = TopManager.getDefault ().notify (desc);
if (leaveAloneOpt.equals (result) ||
NotifyDescriptor.CLOSED_OPTION.equals (result)) {
return existing;
} else if (concatOpt.equals (result)) {
byte[] originalData;
byte[] buf = new byte[4096];
int count;
FileLock lock = existing.lock ();
try {
InputStream is = existing.getInputStream ();
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream ((int) existing.getSize ());
try {
while ((count = is.read (buf)) != -1) {
baos.write (buf, 0, count);
}
} finally {
originalData = baos.toByteArray ();
baos.close ();
}
} finally {
is.close ();
}
existing.delete (lock);
} finally {
lock.releaseLock ();
}
FileObject nue = folder.createData (nuename, ext);
lock = nue.lock ();
try {
OutputStream os = nue.getOutputStream (lock);
try {
os.write (originalData);
InputStream is = getFile ().getInputStream ();
try {
while ((count = is.read (buf)) != -1) {
os.write (buf, 0, count);
}
} finally {
is.close ();
}
} finally {
os.close ();
}
} finally {
lock.releaseLock ();
}
// Does not appear to have any effect:
// ((PropertiesDataObject) getDataObject ()).getBundleStructure ().
// oneFileChanged (getHandler ());
return nue;
} else if (overwriteOpt.equals (result)) {
FileLock lock = existing.lock ();
try {
existing.delete (lock);
} finally {
lock.releaseLock ();
}
return super.createFromTemplate (folder, nuename);
} else {
throw new IOException ("unrecognized result option: " + result); // NOI18N
}
}
}
/** Test whether the object may be deleted.
* @return <code>true</code> if it may (primary file can't be deleted)
*/
public boolean isDeleteAllowed() {
// PENDING - better implementation : don't allow deleting Bunlde_en when Bundle_en_US exists
return (!getFile ().isReadOnly ()) && (!basicName.equals(getFile().getName()));
}
/** Test whether the object may be copied.
* @return <code>true</code> if it may
*/
public boolean isCopyAllowed () {
return true;
}
// [PENDING] copy should be overridden because e.g. copy and then paste
// to the same folder creates a new locale named "1"! (I.e. "foo_1.properties")
/* Getter for move action.
* @return true if the object can be moved
*/
public boolean isMoveAllowed() {
return !getFile ().isReadOnly ();
}
/* Getter for rename action.
* @return true if the object can be renamed
*/
public boolean isRenameAllowed () {
return !getFile ().isReadOnly ();
}
/* Help context for this object.
* @return help context
*/
public HelpCtx getHelpCtx() {
return new HelpCtx (PropertiesFileEntry.class);
}
/** Children of a node representing s single properties file.
* Contains nodes representing individual properties. */
class PropKeysChildren extends Children.Keys {
/** Listens to changes on the properties file entry */
private PropertyChangeListener pcl = null;
/** Listens to changes on the property bundle structure */
private PropertyBundleListener pbl = null;
PropKeysChildren() {
super();
}
/** Sets all keys in the correct order */
protected void mySetKeys() {
// use TreeSet because its iterator iterates in ascending order
TreeSet ts = new TreeSet(new KeyComparator());
PropertiesStructure ps = getHandler().getStructure();
if (ps != null) {
for (Iterator it = ps.nonEmptyItems();it.hasNext();) {
Element.ItemElem el = (Element.ItemElem)it.next();
ts.add(el.getKey());
}
}
setKeys(ts);
}
/** Called to notify that the children has been asked for children
* after and that they should set its keys.
*/
protected void addNotify () {
mySetKeys();
// listener
pcl = new PropertyChangeListener () {
public void propertyChange(PropertyChangeEvent evt) {
mySetKeys();
}
}; // end of inner class
PropertiesFileEntry.this.addPropertyChangeListener (new WeakListener.PropertyChange(pcl));
PropertiesFileEntry.this.getHandler().addPropertyChangeListener (new WeakListener.PropertyChange(pcl));
pbl = new PropertyBundleListener () {
public void bundleChanged(PropertyBundleEvent evt) {
switch (evt.getChangeType()) {
case PropertyBundleEvent.CHANGE_STRUCT:
case PropertyBundleEvent.CHANGE_ALL:
mySetKeys();
break;
case PropertyBundleEvent.CHANGE_FILE:
if (evt.getEntryName().equals(getFile().getName()))
// if it's me
mySetKeys();
break;
case PropertyBundleEvent.CHANGE_ITEM:
if (evt.getEntryName().equals(getFile().getName())) {
// the node should fire the change (to its property sheet, for example
KeyNode kn = (KeyNode)findChild(evt.getItemName());
if (kn != null) {
PropertiesStructure ps = getHandler().getStructure();
if (ps != null) {
Element.ItemElem it = ps.getItem(evt.getItemName());
kn.fireChange(new PropertyChangeEvent(kn, Element.ItemElem.PROP_ITEM_VALUE, null, it.getValue()));
kn.fireChange(new PropertyChangeEvent(kn, Element.ItemElem.PROP_ITEM_COMMENT, null, it.getComment()));
}
else
;
}
else
;
// if it's me
// in theory do nothing
//PropKeysChildren.this.refreshKey(evt.getItemName());
}
break;
}
}
}; // end of inner class
((PropertiesDataObject)PropertiesFileEntry.this.getDataObject()).getBundleStructure().
addPropertyBundleListener (new WeakListenerPropertyBundle(pbl));
}
/** Called to notify that the children has lost all of its references to
* its nodes associated to keys and that the keys could be cleared without
* affecting any nodes (because nobody listens to that nodes).
*/
protected void removeNotify () {
setKeys(new ArrayList());
}
protected Node[] createNodes (Object key) {
String itemKey = (String)key;
return new Node[] { new KeyNode(getHandler().getStructure(), itemKey) };
}
} // end of class PropKeysChildren
}
/*
* <<Log>>
* 18 Gandalf-post-FCS1.16.1.0 3/28/00 Jesse Glick Properties files used as
* templates can merge into one another.
* 17 Gandalf 1.16 11/27/99 Patrik Knakal
* 16 Gandalf 1.15 10/23/99 Ian Formanek NO SEMANTIC CHANGE - Sun
* Microsystems Copyright in File Comment
* 15 Gandalf 1.14 10/12/99 Petr Jiricka
* 14 Gandalf 1.13 9/13/99 Petr Jiricka Removed debug println
* 13 Gandalf 1.12 9/10/99 Petr Jiricka Comparator change
* 12 Gandalf 1.11 8/18/99 Petr Jiricka Some fix
* 11 Gandalf 1.10 8/17/99 Petr Jiricka Changes erlated to
* saving
* 10 Gandalf 1.9 8/9/99 Petr Jiricka Removed debug prints
* 9 Gandalf 1.8 6/24/99 Petr Jiricka
* 8 Gandalf 1.7 6/11/99 Petr Jiricka
* 7 Gandalf 1.6 6/10/99 Petr Jiricka
* 6 Gandalf 1.5 6/9/99 Ian Formanek ---- Package Change To
* org.openide ----
* 5 Gandalf 1.4 6/8/99 Petr Jiricka
* 4 Gandalf 1.3 6/6/99 Petr Jiricka
* 3 Gandalf 1.2 5/14/99 Petr Jiricka
* 2 Gandalf 1.1 5/13/99 Petr Jiricka
* 1 Gandalf 1.0 5/12/99 Petr Jiricka
* $
*/